package com.smsc.headend.task.engine.facade.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.constant.DeviceModel;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dto.DLMSSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.data.enums.OperationName;
import com.smsc.headend.module.data.query.ModelDataItemQueryItem;
import com.smsc.headend.module.fwu.entity.TemplateFwUpgrade;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.enums.DeviceActionType;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.smsc.headend.module.data.enums.OperationName.*;

@Service
@Slf4j
@CacheConfig(cacheNames = "ute-data-item")
public class DataItemFacadeImpl implements DataItemFacade {

    @Autowired
    @Lazy
    DataItemFacade selfService;

    Set<OperationName> getByOperationOperationNames = Collections.unmodifiableSet(new HashSet<OperationName>() {{
        add(GetImageNotTransferLastBlock);
        add(GetFwImageBlockSize);
        add(GetImageTransferStatus);
        add(GetImagesToActivate);
        add(ImageTransferInit);
        add(ImageBlockTransfer);
        add(ImageVerify);
        add(ImageActivate);
        add(ReadDeviceClock);
        add(SetDeviceClock);
        add(MeterUpgradeSettings);
        add(DCUUpgradeSettings);
        add(GetControlMode);
        add(SetControlModeHardReconnect);
        add(SetControlModeSoftReconnect);
        add(RemoteControlAction);
        add(SetControlModeOriginal);
        add(DisconnectAction);
        add(GetControlState);
        add(GetCalendarNamePassive);
        add(GetDayProfilePassive);
        add(GetWeekProfilePassive);
        add(GetSeasonProfilePassive);
        add(GetCalendarNameActive);
        add(GetDayProfileActive);
        add(GetWeekProfileActive);
        add(GetSeasonProfileActive);
        add(GetActivateCalendarTime);
        add(GetSpecialDays);
        add(GetFriendlyDays);
        add(SetCalendarName);
        add(SetDayProfile);
        add(SetWeekProfile);
        add(SetSeasonProfile);
        add(SetActivateCalendarTime);
        add(SetSpecialDays);
        add(SetFriendlyDays);
        add(ActionActivateCalendar);
        add(GetMonitorValue);
        add(GetThresholdNormal);
        add(GetThresholdEmergency);
        add(GetMinOverThresholdDuration);
        add(GetMinUnderThresholdDuration);
        add(GetEmergencyProfile);
        add(GetEmergencyProfileGroupIDList);
        add(GetLoadLimitActions);
        add(SetMonitorValue);
        add(SetThresholdNormal);
        add(SetThresholdEmergency);
        add(SetMinOverThresholdDuration);
        add(SetMinUnderThresholdDuration);
        add(SetEmergencyProfile);
        add(SetEmergencyProfileGroupIDList);
        add(SetLoadLimitActions);
        add(SetDcuCollScheme);
        add(SetDcuMeterTask);
        add(GetDcuCollScheme);
        add(GetDcuMeterTask);
        add(MModUpgradeSettings);
        add(SModUpgradeSettings);
        add(SetMeterPayment);
        add(GetMeterPayment);
        add(SetTimeZone);
        add(SetDaylightSavingsBegin);
        add(SetDaylightSavingsEnd);
        add(SetDaylightSavingsDeviation);
        add(SetDaylightSavingsEnabled);
        add(SetTimeShiftEventLimit);
        add(SetTimeShiftInvalidLimit);
        add(GetTimeZone);
        add(GetDaylightSavingsBegin);
        add(GetDaylightSavingsEnd);
        add(GetDaylightSavingsDeviation);
        add(GetDaylightSavingsEnabled);
        add(GetTimeShiftEventLimit);
        add(GetTimeShiftInvalidLimit);
    }});
    Set<OperationName> getByOperationOperationAndSeqIdNames = Collections.unmodifiableSet(new HashSet<OperationName>() {{
        add(GetDcuClockOverThreshold);
        add(GetDcuMonthOverThreshold);
        add(GetDcuBroadcastTimeCheck);
        add(GetDcuDeviceName);
        add(GetDcuCommunicationAddr);
        add(GetDcuGPRSParam);
        add(GetDcuEthernetParam);
        add(GetDcuBasicComParam);
        add(GetDcuSignalStrength);
        add(GetDcuLpDensity);
        add(SetDcuClockOverThreshold);
        add(SetDcuMonthOverThreshold);
        add(SetDcuBroadcastTimeCheck);
        add(SetDcuGPRSParam);
        add(SetDcuEthernetParam);
        add(SetDcuBasicComParam);
        add(SetDcuLpDensity);
        add(SetDcuReboot);
        add(FtpParametersSet);
        add(FtpParametersGet);
        add(GetNtpActivated);
        add(GetNtpServerAddr);
        add(GetNtpServerPort);
        add(SetNtpActivated);
        add(SetNtpServerAddr);
        add(SetNtpServerPort);
        add(GetGprsModemApn);
        add(GetDcuAutoConnect);
        add(SetGprsModemApn);
        add(SetDcuAutoConnect);
        add(GetHeartBeatCycle);
        add(SetHeartBeatCycle);
        add(GetAutoRegisterMode);
        add(SetAutoRegisterMode);
        add(GetRefreshMeterStatus);
        add(SetRefreshMeterStatus);
        add(GetNetworkOnlineMeterList);
        add(GetNetWorkTopologyInformation);
        add(SetLocalSecretKey);
        add(SetDcuPushStatus);
        add(GetDcuPushStatus);
    }});


    @Autowired
    FeignAssetManagementService assetManagementRemoteService;

    /**
     * @param meter
     * @param comEntity
     * @param dictionary
     * @param paramMap         atomicTask关联的所有参数 {@link com.smsc.headend.task.engine.executor.TaskExecutor}中的getParameterMap方法初始化。
     *                         固定key -> atomicTask,value为AtomicTask对象
     * @param comDeviceModelId
     * @return
     */
    public List<DataItemDTO> getDataItemsByOperation(Meter meter, ComEntity comEntity, DeviceOperation dictionary, Map paramMap, Long comDeviceModelId) {
        OperationName operationName = OperationName.forName(dictionary.getOptName());
        switch (operationName) {
            case ReadData:
            case GetFirmWareVersion:
            case ReadDeviceFirmwareVersion:
            case GeneralParamGet:
                return getReadAttributeDataItem(meter, paramMap, comDeviceModelId, DeviceActionType.Get);
            case GeneralParamSet:
            case SetData:
            case SetProfileCaptureObject:
                return getReadAttributeDataItem(meter, paramMap, comDeviceModelId, DeviceActionType.Set);
            case GeneralParamAction:
            case SetProfileCapture:
                return getReadAttributeDataItem(meter, paramMap, comDeviceModelId, DeviceActionType.Action);
            case ReadWithList:
                return getReadWithListDataItem(meter, paramMap, DeviceActionType.Get);
            case ReadLoadProfile:
            case GetOffset:
            case AnsiReadLoadProfile:
            case AnsiReadLogBook:
                return getReadProfileAttributeDataItem(meter, paramMap, DeviceActionType.Get, comDeviceModelId);
            case GetLpSetting:
                return getLpSettingDataItem(meter, paramMap, DeviceActionType.Get, comDeviceModelId);
            case GetLpStatus:
                return getLpStatusDataItem(meter, paramMap, DeviceActionType.Get, comDeviceModelId);
            case ReadDCUCollectionData:
                return getDataItemByOperation(null, paramMap, comDeviceModelId);
            case MeterUpgradeSettings:
                if (paramMap.containsKey("transferType")) {
                    Integer transferType = MapUtil.getInt(paramMap, "transferType");
                    if (TemplateFwUpgrade.TransferType.Broadcast.getCode() == transferType && comDeviceModelId.equals(Convert.toLong(DeviceModel.DF6203))) {
                        AtomicTask atomicTask = (AtomicTask) paramMap.get(ATOMIC_TASK);
                        return assetManagementRemoteService.queryModelOperationDataItemCosem(comDeviceModelId,
                                OperationGroup.DCUFtpSettings.getId().longValue(), DCUUpgradeSettings.getId().longValue(),
                                atomicTask.getSeq(), null);
                    }
                }
                return getDataItemByOperation(meter, paramMap, comDeviceModelId);
            default:
                if (getByOperationOperationNames.contains(operationName)) {
                    return getDataItemByOperation(meter, paramMap, comDeviceModelId);
                }

                if (getByOperationOperationAndSeqIdNames.contains(operationName)) {
                    return getDataItemBySeqIdOperation(meter, paramMap, comDeviceModelId);
                }
        }
        return null;
    }

    /**
     * 根据型号，subDataItemId和操作类型获取DataItemDTO
     *
     * @param modelId
     * @param deviceActionType
     * @param subDataItemId
     * @return
     */
    @Override
    public List<DataItemDTO> getDataItemsById(Long modelId, DeviceActionType deviceActionType, Long subDataItemId) {
        return selfService.getModelDataItemCosems(modelId, Collections.singletonList(subDataItemId), deviceActionType);
    }


    private List<DataItemDTO> getDataItemByOperation(Meter meter, Map paramMap, Long comDeviceModelId) {
        AtomicTask atomicTask = (AtomicTask) paramMap.get(ATOMIC_TASK);

        Long modelId = null;
        //获取电表的ImageTransfer init对象
        if (meter != null) {
            modelId = meter.getModelId();
        }
        //获取Terminal设备的ImageTransfer init对象
        else {
            modelId = comDeviceModelId;
        }
        return assetManagementRemoteService.queryModelOperationDataItemCosem(modelId, atomicTask.getGroupId(), atomicTask.getOperationId(), atomicTask.getSeq(), null);
    }

    private List<DataItemDTO> getDataItemBySeqIdOperation(Meter meter, Map paramMap, Long comDeviceModelId) {
        AtomicTask atomicTask = (AtomicTask) paramMap.get(ATOMIC_TASK);
        Long modelId = null;
        if (meter != null) {
            modelId = meter.getModelId();
        } else {
            modelId = comDeviceModelId;
        }
        return assetManagementRemoteService.queryModelOperationDataItemCosem(modelId, atomicTask.getGroupId(), atomicTask.getOperationId(), atomicTask.getSeq(), null);
    }


    private List<DataItemDTO> getLpSettingDataItem(Meter meter, Map<String, Object> paramJson, DeviceActionType type, Long comDeviceModelId) {
        if (paramJson == null || !paramJson.containsKey(DATA_ITEM_ID)) {
            log.error("param exception {}", paramJson);
            return null;
        }
        Long profileBufferSubDataItemId = Long.valueOf(paramJson.get(DATA_ITEM_ID) + "104");
        Long modelId = null;
        if (meter != null) {
            modelId = meter.getModelId();
        } else {
            modelId = comDeviceModelId;
        }
        return getModelDataItemCosems(modelId, Lists.newArrayList(profileBufferSubDataItemId), type);
    }

    private List<DataItemDTO> getLpStatusDataItem(Meter meter, Map<String, Object> paramJson, DeviceActionType type, Long comDeviceModelId) {
        if (paramJson == null || !paramJson.containsKey(DATA_ITEM_ID)) {
            log.error("param exception {}", paramJson);
            return null;
        }
        Long profileBufferSubDataItemId = Long.valueOf(paramJson.get(DATA_ITEM_ID) + "112");
        Long modelId = null;
        if (meter != null) {
            modelId = meter.getModelId();
        } else {
            modelId = comDeviceModelId;
        }
        return selfService.getModelDataItemCosems(modelId, Lists.newArrayList(profileBufferSubDataItemId), type);
    }

    private List<DataItemDTO> getReadProfileAttributeDataItem(Meter meter, Map<String, Object> paramJson, DeviceActionType type, Long comDeviceModelId) {
        if (paramJson == null || !paramJson.containsKey(DATA_ITEM_ID)) {
            log.error("param exception {}", paramJson);
            return null;
        }
        Long profileBufferSubDataItemId = Long.valueOf(paramJson.get(DATA_ITEM_ID) + "102");
        Long modelId = null;
        if (meter != null) {
            modelId = meter.getModelId();
        } else {
            modelId = comDeviceModelId;
        }
        return selfService.getModelDataItemCosems(modelId, Lists.newArrayList(profileBufferSubDataItemId), type);
    }

    private List<DataItemDTO> getReadWithListDataItem(Meter meter, Map<String, String> parameterMap, DeviceActionType type) {
        if (parameterMap == null || !parameterMap.containsKey(DATA_ITEM_ID_LIST)) {
            log.error("param exception {}", parameterMap);
            return null;
        }
        List<Long> dataItemIdList = JSONUtil.toBean(parameterMap.get(DATA_ITEM_ID_LIST), List.class);
        return getModelDataItemCosems(meter.getModelId(), dataItemIdList, type);
    }

    private List<DataItemDTO> getReadAttributeDataItem(Meter meter, Map<String, Object> parameterMap, Long comDeviceModelId, DeviceActionType type) {
        if (parameterMap == null) {
            return null;
        }
        Long modelId = meter != null ? meter.getModelId() : comDeviceModelId;
        //No dataItemName send
        AtomicTask atomicTask = (AtomicTask) parameterMap.get(ATOMIC_TASK);
        if (OperationGroup.ReadRegister.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.FirmwareImageTransfer.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.FirmwareImageActivate.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.FirmwareVersionRead.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.GetFirmwareVersion.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.FirmwareVersionVerify.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.GeneralParametersGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.GeneralParametersSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.GeneralParametersAction.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.SetDisplay.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.GetDisplay.getId() == atomicTask.getGroupId().longValue()) {
            Long dataItemId = getDataItemFromParam(parameterMap);
            if (dataItemId == null) {
                log.error("param exception {}", parameterMap);
                return null;
            }
            return getModelDataItemCosems(modelId, Lists.newArrayList(dataItemId), type);
        } else if (OperationGroup.ODRReadProfiles.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.OnDemandMonthlyBillingProfile.getId() == atomicTask.getGroupId().longValue()) {
            Long dataItemId = Long.valueOf(String.valueOf(parameterMap.get(DATA_ITEM_ID)));
            List<DataItemDTO> dataItems = getModelDataItemCosems(modelId, Lists.newArrayList(dataItemId), type);
            DataItemDTO subDataItemDTO = dataItems.get(0);
            if (subDataItemDTO instanceof DLMSSubDataItemDTO) {
                if (atomicTask.getSeq() == 0) {
                    //period
                    ((DLMSSubDataItemDTO) subDataItemDTO).setAttributeId(4);
                } else if (atomicTask.getSeq() == 1) {
                    //capture objects
                    ((DLMSSubDataItemDTO) subDataItemDTO).setAttributeId(3);
                }
            }
            return dataItems;
        } else if (OperationGroup.DCUGetMeterList.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.DCUSetMeterList.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.DCUDeleteMeterList.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.AlarmFilterGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.AlarmFilterSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.PlcOnlineList.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffActiveRatePriceGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffActiveDateGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffActiveStepGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffBackUpRatePriceGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffBackUpStepGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StPaymentControlConfigGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StFunctionControlConfigGet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffActiveDateSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffBackUpRatePriceSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StepTariffBackUpStepSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StPaymentControlConfigSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.StFunctionControlConfigSet.getId() == atomicTask.getGroupId().longValue()
                || OperationGroup.FrozenMonthlyBillingProfile.getId() == atomicTask.getGroupId().longValue()) {
            return getDataItemByOperation(meter, parameterMap, comDeviceModelId);
        }

        return null;
    }

    private Long getDataItemFromParam(Map<String, Object> parameterMap) {
        return parameterMap.containsKey(DATA_ITEM_ID) ? Long.valueOf(String.valueOf(parameterMap.get(DATA_ITEM_ID))) :
                parameterMap.containsKey(SUB_DATA_ITEM_ID) ? Long.valueOf(String.valueOf(parameterMap.get(SUB_DATA_ITEM_ID)))
                        : null;
    }

    @Override
    @Cacheable(key = "#modelId + '.' + #dataItemIds + '.' + #deviceActionType.code", unless = "#result == null")
    public List<DataItemDTO> getModelDataItemCosems(Long modelId, List<Long> dataItemIds, DeviceActionType deviceActionType) {
        ModelDataItemQueryItem modelDataItemQueryItem = new ModelDataItemQueryItem();
        modelDataItemQueryItem.setModelId(modelId);
        modelDataItemQueryItem.setDataItemIds(dataItemIds);
        modelDataItemQueryItem.setDeviceActionType(deviceActionType.getCode());

//        modelDataItemQueryItem.setDataItemNames(dataItemNameList);
        return assetManagementRemoteService.queryModelDataItemCosem(modelDataItemQueryItem);
    }


}
